/****************************************************************************
* Copyright (c) 2005-2006 Jeremy Dowdall
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* 	 IBM Corporation - SWT's CCombo was relied upon _heavily_ for example and reference
*    Jeremy Dowdall <jeremyd@aspencloud.com> - initial API and implementation
*****************************************************************************/

package org.aspencloud.widgets;

import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Layout;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.TypedListener;
import org.eclipse.swt.widgets.Widget;


/**
 * The DropCombo is an abstract class which extends a DropButton to add a
 * a text field and functionality to combine the field with whatever components a
 * subclass may put on the content area of the popup shell.<br>
 * In addition, the DropCombo provides for a  button style of BUTTON_AUTO, 
 * where the button will be hidden if the widget does not have the focus, but will 
 * appear when the widget gains the focus. This is useful when an interface contains 
 * a large number of drop_down combos but is primarily used for viewing, with the 
 * occational edit here and there.
 */
public abstract class DropCombo extends DropButton {
	
	private class DropComboLayout extends Layout {
		protected Point computeSize(Composite composite, int wHint, int hHint, boolean flushCache) {
			Point size = text.computeSize(SWT.DEFAULT, SWT.DEFAULT);
			if(button.getVisible()) {  // use "get" rather than "is" so it works even when not actually showing
				size.x += button.computeSize(SWT.DEFAULT, SWT.DEFAULT).x;
			}

			size.y += textMarginHeight;

			if(wHint != SWT.DEFAULT) {
				size.x = Math.min(size.x, wHint);
			}
			if(hHint != SWT.DEFAULT) {
				size.y = Math.min(size.y, hHint);
			}
			return size;
		}
		
		protected void layout(Composite composite, boolean flushCache) {
			Rectangle cRect = composite.getClientArea();

			Point tSize = text.computeSize(SWT.DEFAULT, SWT.DEFAULT);
			tSize.y += textMarginHeight;
			
			Point bSize;
			if(button.getVisible()) {  // use "get" rather than "is" so it works even when not actually showing
				bSize = button.computeSize(SWT.DEFAULT, SWT.DEFAULT, flushCache);
				bSize.y = Math.min(bSize.y, Math.min(tSize.y, cRect.height));
				bSize.x = Math.min(bSize.y, cRect.width);
				if(cRect.width == bSize.x) {
					bSize.x = cRect.width / 2;
					// if text has a zero width, bad things happen :(
				}
			} else {
				bSize = new Point(0,0);
			}
			
			if(leftAlign) {
				text.setBounds(
						cRect.x+bSize.x,
						cRect.y + (ACW.win32 ? getBorderWidth() : 0),
						cRect.width-bSize.x,
						cRect.height
						);
				button.setBounds(
						cRect.x,
						cRect.y+((cRect.height-bSize.y) / 2),
						bSize.x,
						bSize.y
						);
			} else {
				text.setBounds(
						cRect.x,
						cRect.y + (ACW.win32 ? getBorderWidth() : 0),
						cRect.width-bSize.x,
						cRect.height
						);
				button.setBounds(
						cRect.x+cRect.width-bSize.x,
						cRect.y+((cRect.height-bSize.y) / 2),
						bSize.x,
						bSize.y
						);
			}
		}
	}
	
	protected static final int textMarginHeight = ACW.win32 ? 4 : 0;
	
	protected Text text;
	private int buttonVisibility;

	
	public DropCombo(Composite parent, int style) {
		super(parent, style);
		setLayout(new DropComboLayout());
		
		if(ACW.win32) setBackground(getDisplay().getSystemColor(SWT.COLOR_WHITE));
		
		leftAlign = ((style & ACW.BUTTON_LEFT) != 0);

		int textStyle = SWT.SINGLE;
		if(ACW.gtk) textStyle |= SWT.BORDER;
		if((style & ACW.TEXT_RIGHT) != 0) textStyle |= SWT.RIGHT_TO_LEFT;
		else if((style & ACW.TEXT_LEFT) != 0) textStyle |= SWT.LEFT_TO_RIGHT;

		text = new Text(this, textStyle);
		if(!leftAlign) {
			// DropCombo extends DropButton, meaning the button has already been
			//  created and added to the drawing order.
			// To have Right alignment, this drawing order must be reversed.
			text.moveAbove(button);
		}
		
		int[] textEvents = { SWT.FocusIn, SWT.KeyDown, SWT.Modify };
		for(int i = 0; i < textEvents.length; i++) text.addListener(textEvents[i], listener);

		setPositionControl(this);

//		if((style & SWT.SIMPLE) != 0) buttonVisibility = ACW.BUTTON_NEVER;
//		else buttonVisibility = ACW.BUTTON_ALWAYS;  // defaults to a drop_down combo box
	}


	/**
	 * Adds the listener to the collection of listeners who will
	 * be notified when the receiver's text is modified, by sending
	 * it one of the messages defined in the <code>ModifyListener</code>
	 * interface.<br>
	 *<p>Note: event.data will hold implementation specific data<br>
	 *ie: CDatepickerCombo places its current Date object here</p><br>
	 * @param listener the listener which should be notified
	 *
	 * @exception IllegalArgumentException <ul>
	 *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
	 * </ul>
	 * @exception SWTException <ul>
	 *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 * </ul>
	 *
	 * @see ModifyListener
	 * @see #removeModifyListener
	 */
	public void addModifyListener (ModifyListener listener) {
		checkWidget();
		if (listener == null) SWT.error (SWT.ERROR_NULL_ARGUMENT);
		TypedListener typedListener = new TypedListener (listener);
		addListener (SWT.Modify, typedListener);
	}

	protected void controlEvents(Event event) {
		if(text == event.widget) {
			switch (event.type) {
			case SWT.FocusIn:
				handleFocus(event.type, event.widget);
				break;
			case SWT.KeyDown:
				if(event.stateMask == SWT.CTRL && event.keyCode == ' ')
					popUp(true);
				break;
			case SWT.Modify:
				Event e = new Event();
				e.time = event.time;
				setModifyEventProperties(e);
				notifyListeners (SWT.Modify, e);
				break;
			}
		} else {
			if(ACW.gtk) {
				if((button == event.widget) && (SWT.FocusIn == event.type)) {
					setFocus();
					return;
				}
			}
			super.controlEvents(event);
		}
	}
	
	/**
	 * returns the menu for the Text widget of this DropCombo
	 */
	public Menu getMenu() {
		return text.getMenu();
	}

	public String getText() {
		return text.getText();
	}
	
	protected void handleFocus(int type, Widget widget) {
		super.handleFocus(type, widget);
		if(buttonVisibility == ACW.BUTTON_AUTO) {
			setButtonVisible(hasFocus && !isPopped());
		}
	}
	
	public void popUp(boolean pop) {
		super.popUp(pop);
		if(buttonVisibility == ACW.BUTTON_AUTO) {
			setButtonVisible(hasFocus && !pop);
		}
	}

	/**
	 * Removes the listener from the collection of listeners who will
	 * be notified when the receiver's text is modified.
	 *
	 * @param listener the listener which should no longer be notified
	 *
	 * @exception IllegalArgumentException <ul>
	 *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
	 * </ul>
	 * @exception SWTException <ul>
	 *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 * </ul>
	 *
	 * @see ModifyListener
	 * @see #addModifyListener
	 */
	public void removeModifyListener (ModifyListener listener) {
		checkWidget();
		if (listener == null) SWT.error (SWT.ERROR_NULL_ARGUMENT);
		removeListener(SWT.Modify, listener);	
	}

	/**
	 * Set the visibility style of the drop button.
	 * <p>The style will be forced to NEVER if the contents are null</p>
	 * <dl>
	 * <dt><b>Styles:</b></dt>
	 * <dd>ALWAYS, AUTO, MANUAL, NEVER</dd>
	 * </dl>
	 * <dl>
	 * <dt><b>Style ALWAYS:</b></dt>
	 * <dd>Button will always be shown - standard SWT.DROP_DOWN behaviour.  The method
	 * setButtonVisible(boolean) has no affect with this style set</dd>
	 * <dt><b>Style AUTO:</b></dt>
	 * <dd>Button visibility will be handled automatically through focus events, popup events,
	 * as well as programmatically</dd>
	 * <dt><b>Style MANUAL:</b></dt>
	 * <dd>Button visibility will only be handled programmatically</dd>
	 * <dt><b>Style NEVER:</b></dt>
	 * <dd>Button will never be shown - standard SWT.SIMPLE behaviour.  The method
	 * setButtonVisible(boolean) has no affect with this style set</dd>
	 * </dl>
	 * @param visibility the visibility style constant
	 * @see #setButtonVisible(boolean)
	 */
	public void setButtonVisibility(int visibility) {
		if(content == null) visibility = ACW.BUTTON_NEVER;
		buttonVisibility = visibility;
		setButtonVisible(false);
	}

	/**
	 * Set the visible state of the button
	 * <p>Note: This method is only useful when the button's visibility style is 
	 * either AUTO or MANUAL.</p>
	 * @param visible
	 * @see #setButtonVisibility(int)
	 */
	protected void setButtonVisible(boolean visible) {
		switch (buttonVisibility) {
		case ACW.BUTTON_ALWAYS:
			visible = true;
			break;
		case ACW.BUTTON_NEVER:
			visible = false;
			break;
		}
		
		button.setVisible(visible);

		layout(true);
		update();
	}

	public void setEditable(boolean editable) {
		text.setEditable(editable);
	}
	
	public void setEnabled(boolean enabled) {
		text.setEnabled(enabled);
		button.setEnabled(enabled);
	}

	/**
	 * sets focus to the text widget
	 */
	public boolean setFocus() {
		return text.setFocus();
	}

	/**
	 * Sets the menu for the Text widget of this DropCombo
	 * <p>Note that setting the menu to null causes the native menu to be used</p>
	 * <p>If the intent is to disable the menu, then set it to a blank menu</p>
	 */
	public void setMenu(Menu menu) {
		text.setMenu(menu);
	}

	protected void setModifyEventProperties(Event e) {	}

	public void setRedraw(boolean redraw) {
		super.setRedraw(redraw);
		text.setRedraw(redraw);
		button.setRedraw(redraw);
	}
	
	/**
	 * stretch is true and stretchControl is currently null, then sets the stretchControl to itself.
	 * <p>if stretch is false - sets the stretchControl to null<p>
	 * @param stretch
	 */
	public void setStretch(boolean stretch) {
		if(stretch && getStretchControl() == null) {
			setStretchControl(this);
		} else if(!stretch){
			setStretchControl(null);
		}
	}
}
